#coding:utf-8
#version: 1.0-20220906
import re, time, datetime
import os, os.path, shutil
from ftplib import FTP, error_perm
from configparser import ConfigParser

class SyncFile(FTP):

    encoding = "utf-8"  # 默认编码
    dir_pattern = re.compile(r'[a-zA-Z]{3}\s+\d{2}\s+[\d:]{4,}\s(.*)$')
    file_pattern = re.compile(r'(\d+)\s+[a-zA-Z]{3}\s+\d{2}\s+[\d:]{4,}\s(.*)$')

    
    def set_host(self, ftp_host, port, ftp_root_path):
        self.__ftp_host = ftp_host
        self.__ftp_port = port 
        self.__ftp_root_path = ftp_root_path
    
    def set_user_pwd(self, ftp_user=None, ftp_pwd=None):
        conf = ConfigParser()
        import pathlib
        conf.read(pathlib.Path.home() / 'ftp.ini')
        # print(conf.sections())
        # print(conf['ftp200']['username'])
        self.__ftp_user = ftp_user if ftp_user else conf['ftp200']['username']
        self.__ftp_pwd = ftp_pwd if ftp_pwd else conf['ftp200']['password']

    def __get_ftp_dirs(self, *args):
        '''拷贝了 nlst() 和 dir() 代码修改，返回详细信息而不打印'''
        cmd = 'LIST'
        # func = None
        # if args[-1:] and type(args[-1]) != type(''):
        #     args, func = args[:-1], args[-1]
        for arg in args:
            cmd = cmd + (' ' + arg)
        files = []

        def filter(file):
            #print(file)
            if file.startswith('d'):
                file = self.dir_pattern.findall(file)[0]
                files.append(file)

        self.retrlines(cmd, filter)
        return files

    def __get_ftp_files(self, *args):
        """返回文件列表，简要信息"""
        cmd = 'LIST'
        for arg in args:
            cmd = cmd + (' ' + arg)
        files = []

        def filter(file):
            # print(file)
            if file.startswith('-'):
                # print(file)
                # r'(\d+)\s+[a-zA-Z]{3}\s+\d{2}\s+[\d:]{4,}\s(.*)$'
                match_file = self.file_pattern.findall(file)[0]
                files.append(match_file)

        self.retrlines(cmd, filter)
        return files
        
    def __list_local_files(self, dirpath):
        dirs = []
        files = []
        for filename in os.listdir(dirpath):
            path = dirpath + '/' + filename
            if os.path.isfile(path):
                files.append(filename)
            else:
                dirs.append(filename)
        return dirs, files


    def __sync_ftp_files(self, curr_ftp_path):
            
        # 远程目录不存在
        try:
            self.cwd(self.__ftp_root_path)
        except error_perm:
            self.__ftp_root_path = input("请输入需要同步的目录:")
            self.__sync_ftp_files(self.__ftp_root_path)
            return

        r_dirs = self.__get_ftp_dirs(curr_ftp_path)
        r_file_tuples = self.__get_ftp_files(curr_ftp_path)
        r_files = [x[1] for x in r_file_tuples]
        # print(r_dirs)
        # print(r_files)

        curr_local_path = curr_ftp_path.replace(self.__ftp_root_path, self.__local_root_path)
        l_dirs, l_files = self.__list_local_files(curr_local_path)
        # print(l_dirs)
        # print(l_files)

        print('\n----------------' + curr_ftp_path + '----------------')
        # 删除 本地多余的文件
        del_files = list(set(l_files).difference(set(r_files)))
        for f in del_files:
            local_file = curr_local_path+'/'+f
            print('删除文件：' + local_file)
            os.remove(local_file)

        # 下载 ftp多出的文件
        add_files = list(set(r_files).difference(set(l_files)))
        for f in add_files:
            self.__download_file(f, curr_ftp_path, curr_local_path)
        self.set_debuglevel(0)

        # 上传 本地最近修改的文件
        cmp_files = list(set(l_files).intersection(set(r_files)))
        for file_path in cmp_files:
            op = self.__local_is_newer(file_path, r_file_tuples, curr_ftp_path, curr_local_path)
            if op=="UPLOAD":
                try:
                    print(">>>上传", file_path)
                    local_file, remote_file = curr_local_path + '/' + file_path,  curr_ftp_path + '/' + file_path
                    with open(local_file, 'rb') as file :
                        self.cwd(curr_ftp_path) # 覆盖上传
                        self.storbinary('STOR %s' % os.path.basename(remote_file), file)
                except Exception as err:
                    print(err)
            elif op=="DOWNLOAD":
                self.__download_file(file_path, curr_ftp_path, curr_local_path)

        # 删除 本地多余的目录
        del_dirs = list(set(l_dirs).difference(set(r_dirs)))
        for f in del_dirs:
            dp = curr_local_path+'/'+f
            print('删除目录：' + dp)
            shutil.rmtree(dp)

        # 下载 ftp多出的目录
        add_dirs = list(set(r_dirs).difference(set(l_dirs)))
        for d in add_dirs:
            dp = curr_local_path+'/'+ d
            print('创建目录：' + dp)
            os.mkdir(dp)
            self.__sync_ftp_files(curr_ftp_path+'/'+ d)

        # 检查已经存在ftp是否和本地相同
        for dp in r_dirs:
            self.__sync_ftp_files(curr_ftp_path + '/' + dp)

    def __download_file(self, file_path, curr_ftp_path, curr_local_path):
        fp = open(curr_local_path + '/' + file_path, 'wb')
        bufsize = 1024
        remote_file = curr_ftp_path+'/' + file_path
        print('>>>下载：' + remote_file)
        self.retrbinary('RETR ' + remote_file, fp.write, bufsize)

    def __local_is_newer(self, file_path, r_file_tuples, curr_ftp_path, curr_local_path):
        remote_file = curr_ftp_path + '/' + file_path
        # size_r = self.size(remote_file)
        size_r = int([x[0] for x in r_file_tuples if x[1]==file_path][0])

        local_file = curr_local_path + '/' + file_path
        size_l = os.path.getsize(local_file)

        if size_r!=size_l:
            mtime_r = self.sendcmd("MDTM "+remote_file)[4:].strip()
            mtime_r = datetime.datetime.strptime(mtime_r, '%Y%m%d%H%M%S')
            tz_offset = -1*time.timezone if (time.localtime().tm_isdst == 0) else time.altzone
            mtime_r = mtime_r.timestamp() + tz_offset
            mtime_l = os.path.getmtime(local_file)

            if mtime_l > mtime_r:
                print("本地新：", file_path, size_l, mtime_l, ">", size_r, mtime_r)
                return "UPLOAD"
            else:
                print("远程新：", file_path, size_l, mtime_l, "<", size_r, mtime_r)
                return "DOWNLOAD"
        else:
            return "NO-OP"

    def sync(self, local_root_path=os.getcwd()):
        self.__local_root_path = local_root_path 
        self.connect(self.__ftp_host, self.__ftp_port)  # 连接
        self.login(self.__ftp_user, self.__ftp_pwd)
        self.__sync_ftp_files(self.__ftp_root_path)
        self.quit()  # 退出


if __name__ == '__main__':
    FTP_ROOT_PATH = '/_books/'

    sf = SyncFile()
    sf.set_host("dev.cddyys.com", 94, FTP_ROOT_PATH)
    sf.set_user_pwd()
    sf.sync()
    os.system('pause')